package codeine.command_peer;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import codeine.configuration.ConfigurationManager;
import codeine.configuration.Links;
import codeine.configuration.PathHelper;
import codeine.model.Constants;
import codeine.utils.ExceptionUtils;
import codeine.utils.HttpUtils;
import codeine.utils.ServletUtils;
import codeine.utils.TextFileUtils;
import codeine.utils.ThreadUtils;

import com.google.inject.Inject;

public class AllNodesCommandExecuter {
	
	private static final Logger log = Logger.getLogger(AllNodesCommandExecuter.class);

	@Inject	private Links links;
	@Inject	private ConfigurationManager configurationManager;
	@Inject	private PathHelper pathHelper;
	private int total;
	private int count;
	private int fail;
	private Pattern pattern = Pattern.compile(".*"+Constants.COMMAND_RESULT+"(-?\\d+).*"); 
	private Set<String> failedNodes = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
	private Writer writer;
	
	public void executeOnAllNodes(HttpServletResponse response, String params, String projectName, String command,
			List<String> peersList) {
		total = peersList.size();
		int concurrency = configurationManager.getProjectForName(projectName).getCommand(command).concurrency();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
		String fileName = String.valueOf(sdf.format(new Date()));
		writer = TextFileUtils.getWriter(new File(pathHelper.getPluginsOutputDir(projectName) + "/" + fileName), false);
		log.info("running command "+command+" with concurrency " + concurrency);
		writeLine("running command "+command+" with concurrency " + concurrency, response);
		writeLine("running command on nodes " + peersList, response);
		ExecutorService executor = Executors.newFixedThreadPool(concurrency);
		for (String peer : peersList) {
			commandNode(executor, peer, projectName, command, params, response);
		}
		executor.shutdown();
		while (!executor.isTerminated()) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		}
		writeLine("finished!", response);
		writeLine("failed nodes: " + failedNodes, response);
		writeLine("=========> aggregate-command-statistics (fail/total): " + fail + "/" + total + "\n", response);
	}


	private long getSleepTime(String projectName) {
		return 100;
	}

	private void commandNode(ExecutorService executor, String hostport, String project, String command, String params,
			HttpServletResponse res) {
		String link = links.getPeerCommandLink(hostport, project, command, params);
		log.info("commandNode link is " + link);
		PeerCommandThread worker = new PeerCommandThread(link, res, project, hostport);
		executor.execute(worker);
	}
	private void writeLine(String line, HttpServletResponse response) {
		writeLineToHtml("<pre>" + line + "</pre>", response);
		writeLineToFile(line);
	}

	private synchronized void writeLineToHtml(String line, HttpServletResponse res) {
		ServletUtils.getWriter(res).println(line);
		ServletUtils.getWriter(res).flush();
	}

	private synchronized void writeLineToFile(String line) {
		try {
			writer.append(line + "\n");
			writer.flush();
		} catch (IOException e) {
			throw ExceptionUtils.asUnchecked(e);
		}
	}

	private class PeerCommandThread implements Runnable {
		private final String link;
		private HttpServletResponse resspose;
		private String project;
		private String node;

		public PeerCommandThread(String link, HttpServletResponse response, String project, String node) {
			this.link = link;
			resspose = response;
			this.project = project;
			this.node = node;
		}

		@Override
		public void run() {
			try {
				ThreadUtils.sleep(getSleepTime(project));
				log.info("running worker " + link);
				String result = HttpUtils.doGET(link);
				if (null != result) {
					writeLine("executed on node" + node + ", result:\n" + result, resspose);
					Matcher matcher = pattern.matcher(result.replace("\n", ""));
					if (!matcher.matches() || 0 != Integer.valueOf(matcher.group(1))){
						fail();
					}
				} else {
					log.warn("result is null");
					fail();
				}
			} catch (Exception ex) {
				log.warn("error in restart with link " + link + " ; message "
						+ ExceptionUtils.getRootCause(ex).getMessage());
				log.debug("error details", ex);
				fail();
			}
			count++;
			writeLineToHtml("<script>$( '#progressbarall' ).progressbar({value: "+count+"});</script>", resspose);
			writeLineToHtml("<script>$( '#progressbarfailed' ).progressbar({value: "+fail+"});</script>", resspose);
		}

		private void fail() {
			fail++;
			failedNodes.add(node);
		}
	}
}